/*
	Copyright (C) 2011 Salil Bhagurkar

	This file is part of illusion

	illusion is free software: you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as
	published by the Free Software Foundation, either version 3 of
	the License, or (at your option) any later version.

	illusion is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public
	License along with illusion.
	If not, see <http://www.gnu.org/licenses/>.
*/

#include <kernel/console.h>
#include <kernel/vmpage.h>
#include <kernel/process.h>
#include <kernel/init.h>
#include <kernel/module.h>
#include <apimod/apimod.h>
#include <kernel/syscall.h>
#include <kernel/errors.h>
#include <arch/portio.h>
#include <arch/page.h>
#include <arch/power.h>
#include <arch/asmdecl.h>
#include <klib/lib.h>
#include <klib/string.h>
#include <klib/memory.h>
#include <kernel/sched.h>
#include <kernel/vfs.h>
#include <kernel/list.h>
#include <kernel/kmalloc.h>
#include <kernel/build.h>
#include <kernel/input.h>
#include <kernel/process.h>
#include <kernel/fs.h>

//TODO No kernel code is using open / close when accessing ramdisk shit

static struct process_file *get_process_file(uint_t fd)
{
	struct process_file *pf;
	list_for(current_process->files, pf) {
		if(pf->fid == fd)
			return pf;
	}
	return null;
}

static int sys_open_vfs_node(struct process *process, struct vfs_node *node, uint_t state, uint_t *fd)
{
	//Put it in the state and return a file descriptor
	int err = vfs_open(node, state);
	if(err) {
		return err;
	}
	//Add to the process file list
	struct process_file *pf = (struct process_file *)kmalloc(sizeof(struct process_file), "file");
	pf->fid = process->next_fid++;
	pf->next = pf->prev = null;
	pf->node = node;
	pf->state = state;
	list_attach(process->files, process->files_tail,
			pf);
	*fd = pf->fid;
	return err;
}

static int sys_open(struct process *process, char *path, uint_t state, uint_t *fd)
{
	int err;
	//Find out what vfs_node it is
	struct vfs_node *node;
	err = vfs_get(&node, current_process->vfs_node, path);
	if(err)
		return err;
	return sys_open_vfs_node(process, node, state, fd);
}

static void remove_process_file(struct process_file *pf)
{
	list_detach(current_process->files, current_process->files_tail,
			pf);
	kfree(pf, sizeof(struct process_file), "file");
}

static int sys_close(uint_t fid)
{
	int err;
	//Figure out what vfs_node it is
	struct process_file *pf = get_process_file(fid);
	if(pf == null) {
		err = EPARAM;
	} else {
		err = vfs_close(pf->node, pf->state);
		if(err)
			return err;
		remove_process_file(pf);
	}
	return err;
}

static int sys_changestate(uint_t fid, uint_t state)
{
	int err;
	struct process_file *pf = get_process_file(fid);
	if(pf == null)
		return EPARAM;
	//Close it first
	err = vfs_close(pf->node, pf->state);
	if(err)
		return err;
	err = vfs_open(pf->node, state);
	if(err)
		return err;
	//Assign new state
	pf->state = state;
	return 0;
}

static int sys_read(uint_t fid, char *stream,
		void *buf, uint_t offset,
		uint_t count)
{
	int err = 0;
	struct process_file *pf = get_process_file(fid);
	if(pf == null) {
		err = EPARAM;
	} else {
		//Validate
		if(pf->state == STATE_READ || pf->state == STATE_WRITE) {
			err = vfs_read(pf->node, "data", buf, offset, count);
		} else {
			err = EPERMS;
		}
	}
	return err;
}

static int sys_write(uint_t fid, char *stream,
		void *buf, uint_t offset,
		uint_t count)
{
	int err;
	struct process_file *pf = get_process_file(fid);
	if(pf == null) {
		err = EPARAM;
	} else {
		//Validate
		if(pf->state == STATE_WRITE) {
			err = vfs_write(pf->node, "data", buf,
					offset, count);
		} else {
			err = EPERMS;
		}
	}
	return err;
}

static int sys_getparent(uint_t fid, uint_t *parent_fid)
{
	struct process_file *pf = get_process_file(fid);
	if(!pf)
		return EPARAM;
	struct vfs_node *parent = vfs_get_parent(pf->node);
	if(!parent)
		return ENOENT;

	//Open the parent for read
	int err = vfs_open(parent, STATE_READ);
	if(err)
		return err;

	//Add to the process file list
	pf = (struct process_file *)kmalloc(sizeof(struct process_file), "file");
	pf->fid = current_process->next_fid++;
	pf->next = pf->prev = null;
	pf->node = parent;
	pf->state = STATE_READ;
	list_attach(current_process->files, current_process->files_tail,
			pf);
	*parent_fid = pf->fid;

	return 0;
}

static int sys_getchild(uint_t fid, uint_t *child_fid)
{
	struct process_file *pf = get_process_file(fid);
	if(!pf)
		return EPARAM;
	struct vfs_node *child = vfs_get_child(pf->node);
	if(!child)
		return ENOENT;
	int err;
	err = vfs_open(child, STATE_READ);
	if(err)
		return err;
	//Add to the process file list
	pf = (struct process_file *)kmalloc(sizeof(struct process_file), "file");
	pf->fid = current_process->next_fid++;
	pf->next = pf->prev = null;
	pf->node = child;
	pf->state = STATE_READ;
	list_attach(current_process->files, current_process->files_tail,
			pf);
	*child_fid = pf->fid;
	return 0;
}

static int sys_nextchild(uint_t fid)
{
	struct process_file *pf = get_process_file(fid);
	if(!pf)
		return EPARAM;
	//Close the previous child
	int err = vfs_close(pf->node, pf->state);
	if(err)
		return err;
	struct vfs_node *child = vfs_next_child(pf->node);
	if(!child) {
		remove_process_file(pf);
		return ENOENT;
	}
	err = vfs_open(child, STATE_READ);
	if(err) {
		remove_process_file(pf);
		return err;
	}
	//Modify the process_file
	pf->node = child;
	pf->state = STATE_READ;
	return 0;
}

void sys_exitprocess()
{
	debug("%s exited itself\n", current_process->name);
	event_raise(current_process->evt_exit, null);
	sched_sleep_process(current_process);
	sched_release();
	console_printf("Holy shit!\n");
}

static char *syscall_names[] = {"version", "release", "getpid", "open", "close", "read",
		"write", "rename", "getinfo", "create", "changestate", "readstream",
		"writestream", "getapimod", "print", "readinput", "getchild", "nextchild",
		"args", "createprocess", "exitprocess", "getparent", "getinfo1",
		"getcurrent", "delete", "setcurrent", "getdev", "nextdev",
		"resumeprocess", "pauseprocess", "getpages", "freepages", "redirectout",
		"waitforprocess", "killprocess", "detectfs",
		"mountfs", "foo", "foo_enter", "foo_leave", "try", "exit_try"};

bool in_syscall = 0;

int syscall(uint_t no, u32 param0, u32 param1,
		u32 param2, u32 param3,
		u32 param4)
{
	debug("%s: %u: %s: (%x, %x, %x, %x, %x)\n", current_process->name, no, syscall_names[no],
			param0, param1, param2, param3, param4);
	int ret_err = ERETURN;
	in_syscall = 1;
	switch(no) {
	case SYS_VERSION:
	{
		uint_t *version = (uint_t *)param0;
		*version = (uint_t)(&_BUILD_NUMBER);
		uint_t *date_l = (uint_t *)param1;
		*date_l = (uint_t)(&_BUILD_DATE);
		ret_err = 0;
		goto syscall_cleanup;
	}
	case SYS_RELEASE:
	{
		//TODO Dodgy stuff here. in_syscall is not set after current process
		//returns from syscall
		in_syscall = 0;
		sched_release();
		ret_err = 0;
		goto syscall_cleanup;
	}
	case SYS_GETPID:
	{
		uint_t *pid = (uint_t *)param0;
		*pid = current_process->pid;
		ret_err = 0;
		goto syscall_cleanup;
	}
	case SYS_OPEN:
	{
		char *path = (char *)param0;
		uint_t state = param1;
		uint_t *fd = (uint_t *)param2;
		ret_err = sys_open(current_process, path, state, fd);
		goto syscall_cleanup;
	}
	case SYS_CHANGESTATE:
	{
		uint_t fid = param0;
		uint_t state = param1;
		ret_err = sys_changestate(fid, state);
		goto syscall_cleanup;
	}
	case SYS_CLOSE:
	{
		uint_t fid = param0;
		ret_err = sys_close(fid);
		goto syscall_cleanup;
	}
	case SYS_CREATE:
	{
		uint_t fid = param0;
		char *name = (char *)param1;
		struct process_file *pf = get_process_file(fid);
		if(!pf) {
			ret_err = EPARAM;
			goto syscall_cleanup;
		}
		if(pf->state != STATE_WRITE) {
			ret_err = EPERMS;
			goto syscall_cleanup;
		}
		vfs_create(pf->node, name);
		ret_err = 0;
		goto syscall_cleanup;
	}
	case SYS_READ:
	{
		uint_t fid = param0;
		void *buf = (void *)param1;
		uint_t offset = param2;
		uint_t count = param3;
		ret_err = sys_read(fid, "data", buf, offset, count);
		goto syscall_cleanup;
	}
	case SYS_WRITE:
	{
		uint_t fid = param0;
		void *buf = (void *)param1;
		uint_t offset = param2;
		uint_t count = param3;
		ret_err = sys_write(fid, "data", buf, offset, count);
		goto syscall_cleanup;
	}
	case SYS_READSTREAM:
	{
		uint_t fid = param0;
		char *stream = (char *)param1;
		void *buf = (void *)param2;
		uint_t offset = param3;
		uint_t count = param4;
		ret_err = sys_read(fid, stream, buf, offset, count);
		goto syscall_cleanup;
	}
	case SYS_WRITESTREAM:
	{
		uint_t fid = param0;
		char *stream = (char *)param1;
		void *buf = (void *)param2;
		uint_t offset = param3;
		uint_t count = param4;
		ret_err = sys_write(fid, stream, buf, offset, count);
		goto syscall_cleanup;
	}
	case SYS_GETCHILD:
	{
		uint_t fid = param0;
		uint_t *child_fid = (uint_t *)param1;
		ret_err = sys_getchild(fid, child_fid);
		goto syscall_cleanup;
	}
	case SYS_NEXTCHILD:
	{
		uint_t fid = param0;
		ret_err = sys_nextchild(fid);
		goto syscall_cleanup;
	}
	case SYS_GETPARENT:
	{
		uint_t fid = param0;
		uint_t *parent_fid = (uint_t *)param1;
		ret_err = sys_getparent(fid, parent_fid);
		goto syscall_cleanup;
	}
	case SYS_GETINFO:
	{
		uint_t fid = param0;
		struct sys_info *info = (struct sys_info *)param1;
		struct process_file *pf = get_process_file(fid);
		info->child_count = pf->node->child_count;
		info->length = pf->node->length;
		strcpy(info->name, pf->node->name, SYS_INFO_NAMELEN);
		ret_err = 0;
		goto syscall_cleanup;
	}
	case SYS_READINPUT:
	{
		uint_t *key = (uint_t *)param0;
		uint_t *state = (uint_t *)param1;
		ret_err = input_read_key(key, state);
		goto syscall_cleanup;
	}
	case SYS_PRINT:
	{
		char *str = (char *)param0;
		uint_t len = param1;
		if(current_process->redirect_out) {
			int err = vfs_write(current_process->redirect_out, "data", str,
					current_process->redirect_out_idx, len);
			if(err) {
				ret_err = err;
				goto syscall_cleanup;
			}
			current_process->redirect_out_idx += len;
		} else {
			console_write(str, param1);
		}
		ret_err = 0;
		goto syscall_cleanup;
	}
	case SYS_GETAPIMOD:
	{
		void **get_interface = (void **)param0;
		struct apimod_export *apimod_export =
				(struct apimod_export *)apimod_module.export;
		*get_interface = apimod_export->get_interface;
		ret_err = 0;
		goto syscall_cleanup;
	}
	case SYS_ARGS:
	{
		uint_t *len = (uint_t *)param1;
		*len = current_process->arguments_length;
		char *args = (char *)param0;
		if(args != null)
			memcpy(args, current_process->arguments, current_process->arguments_length);
		ret_err = 0;
		goto syscall_cleanup;
	}
	case SYS_CREATEPROCESS:
	{
		uint_t *pid = (uint_t *)param0;
		char *path = (char *)param1;
		char *arguments = (char *)param2;
		struct vfs_node *node;
		ret_err = vfs_get(&node, current_process->vfs_node, path);
		if(ret_err) {
			goto syscall_cleanup;
		}
		uint_t args_len = 0;
		if(arguments)
			args_len = strlen(arguments) + 1;
		struct process *process;
		int err = create_process(&process, node, arguments, args_len, 1);
		if(!err) {
			*pid = process->pid;
		}
		ret_err = err;
		goto syscall_cleanup;
	}
	case SYS_EXITPROCESS:
	{
		sys_exitprocess();
		break;
	}
	case SYS_GETINFO1:
	{
		uint_t fid = param0;
		struct sys_info1 *info = (struct sys_info1 *)param1;
		struct process_file *pf = get_process_file(fid);
		if(!pf)
			return EPARAM;
		vfs_get_path(info->path, SYS_INFO_PATHLEN, pf->node);
		ret_err = 0;
		goto syscall_cleanup;
	}
	case SYS_GETCURRENT:
	{
		uint_t *fd = (uint_t *)param0;
		ret_err = sys_open_vfs_node(current_process, current_process->vfs_node, STATE_READ, fd);
		goto syscall_cleanup;
	}
	case SYS_DELETE:
	{
		uint_t fd = param0;
		uint_t fd_parent = param1;
		struct process_file *pf_parent = get_process_file(fd_parent);
		if(!pf_parent) {
			ret_err = EPARAM;
			goto syscall_cleanup;
		}
		struct process_file *pf_child = get_process_file(fd);
		if(!pf_child) {
			ret_err = EPARAM;
			goto syscall_cleanup;
		}
		if(pf_parent->state != STATE_WRITE) {
			ret_err = EPERMS;
			goto syscall_cleanup;
		}
		if(pf_child->state != STATE_WRITE) {
			ret_err = EPERMS;
			goto syscall_cleanup;
		}
		if(pf_child->node->parent != pf_parent->node) {
			ret_err = EPERMS;
			goto syscall_cleanup;
		}
		int err = vfs_delete(pf_child->node);
		if(err) {
			ret_err = err;
			goto syscall_cleanup;
		}
		//TODO Create a background thread to manage syncs
		vfs_sync(pf_child->node);
		vfs_dispose_node(pf_child->node, true);
		list_detach(current_process->files, current_process->files_tail,
				pf_child);
		kfree(pf_child, sizeof(struct process_file), "file");
		ret_err = 0;
		goto syscall_cleanup;
	}
	case SYS_SETCURRENT:
	{
		uint_t fid = param0;
		struct process_file *pf = get_process_file(fid);
		if(!pf) {
			ret_err = EPARAM;
			goto syscall_cleanup;
		}
		current_process->vfs_node = pf->node;
		ret_err = 0;
		goto syscall_cleanup;
	}
	case SYS_GETDEV:
	{
		uint_t *fd = (uint_t *)param0;
		if(!devs) {
			ret_err = ENOENT;
			goto syscall_cleanup;
		}
		ret_err = sys_open_vfs_node(current_process, devs->root, STATE_READ, fd);
		goto syscall_cleanup;
	}
	case SYS_NEXTDEV:
	{
		uint_t fid = param0;
		struct process_file *pf = get_process_file(fid);
		if(!pf) {
			ret_err = EPARAM;
			goto syscall_cleanup;
		}

		//Close the previous device
		int err = vfs_close(pf->node, pf->state);
		if(err) {
			ret_err = err;
			goto syscall_cleanup;
		}

		struct vfs_device *next = pf->node->dev->next;
		if(!next) {
			remove_process_file(pf);
			ret_err = ENOENT;
			goto syscall_cleanup;
		}

		err = vfs_open(next->root, STATE_READ);
		if(err) {
			remove_process_file(pf);
			ret_err = err;
			goto syscall_cleanup;
		}
		//Modify the process_file
		pf->node = next->root;
		pf->state = STATE_READ;
		ret_err = 0;
		goto syscall_cleanup;
	}
	case SYS_RESUMEPROCESS:
	{
		uint_t pid = param0;
		struct process *proc = get_process_by_pid(pid);
		if(!proc) {
			ret_err = EPARAM;
			goto syscall_cleanup;
		}
		if(proc->state == PROCESS_STATE_RUN) {
			ret_err = ESTINV;
			goto syscall_cleanup;
		}
		sched_run_process(proc);
		ret_err = 0;
		goto syscall_cleanup;
	}
	case SYS_PAUSEPROCESS:
	{
		uint_t pid = param0;
		struct process *proc = get_process_by_pid(pid);
		if(!proc) {
			ret_err = EPARAM;
			goto syscall_cleanup;
		}
		if(proc->state == PROCESS_STATE_SLEEP) {
			ret_err = ESTINV;
			goto syscall_cleanup;
		}
		sched_sleep_process(proc);
		ret_err = 0;
		goto syscall_cleanup;
	}
	case SYS_GETPAGES:
	{
		void **pages = (void **)param0;
		uint_t count = param1;
		*pages = vm_get_pages(current_process->vmmap, count, PAGE_PRESENT
				| PAGE_WRITE | PAGE_USER);
		if(!pages) {
			ret_err = ERESRC;
			goto syscall_cleanup;
		}
		ret_err = 0;
		goto syscall_cleanup;
	}
	case SYS_FREEPAGES:
	{
		void *pages = (void *)param0;
		uint_t count = param1;
		ret_err = vm_free_pages(current_process->vmmap, pages, count);
		goto syscall_cleanup;
	}
	case SYS_REDIRECTOUT:
	{
		uint_t pid = param0;
		uint_t fd = param1;
		struct process *process = get_process_by_pid(pid);
		if(!process) {
			ret_err = ENOENT;
			goto syscall_cleanup;
		}
		struct process_file *file = get_process_file(fd);
		if(file->state != STATE_WRITE) {
			ret_err = EPERMS;
			goto syscall_cleanup;
		}
		process->redirect_out = file->node;
		process->redirect_out_idx = 0;
		ret_err = 0;
		goto syscall_cleanup;
	}
	case SYS_WAITFORPROCESS:
	{
		unsigned long pid = param0;
		struct process *proc = get_process_by_pid(pid);
		if(!proc) {
			ret_err = EPARAM;
			goto syscall_cleanup;
		}
		void *data;
		event_wait(&data, proc->evt_exit);
		ret_err = 0;
		goto syscall_cleanup;
	}
	case SYS_KILLPROCESS:
	{
		unsigned long pid = param0;
		struct process *proc = get_process_by_pid(pid);
		if(!proc) {
			ret_err = EPARAM;
			goto syscall_cleanup;
		}
		process_kill(proc);
		ret_err = 0;
		goto syscall_cleanup;
	}
	case SYS_DETECTFS:
	{
		char *dev_path = (char *)param0;
		char *fs_path = (char *)param1;
		struct vfs_node *dev_node, *fs_node;
		ret_err = vfs_get(&dev_node, current_process->vfs_node, dev_path);
		if(ret_err)
			goto syscall_cleanup;
		ret_err = vfs_get(&fs_node, current_process->vfs_node, dev_path);
		if(ret_err)
			goto syscall_cleanup;
		ret_err = detect_fs(dev_node, fs_node);
		goto syscall_cleanup;
	}
	case SYS_MOUNTFS:
	{
		char *dev_path = (char *)param0;
		char *fs_path = (char *)param1;
		struct vfs_node *dev_node, *fs_node;
		ret_err = vfs_get(&dev_node, current_process->vfs_node, dev_path);
		if(ret_err)
			goto syscall_cleanup;
		ret_err = vfs_get(&fs_node, current_process->vfs_node, dev_path);
		if(ret_err)
			goto syscall_cleanup;
		ret_err = mount_fs(dev_node, fs_node);
		goto syscall_cleanup;
	}
	case SYS_FOO:
	{
		ret_err = 0;
		console_printf("Free pages = %u\n", phys_count_free());
		console_printf("vm free memory = %u\n", vm_count_free(&kernel_vmmap) * PAGE_SIZE
				+ kmalloc_pool_free_memory());
		kmalloc_debug_traces();
		struct process_file *pf;
		list_for(current_process->files, pf) {
			console_printf("pf = %s\n", pf->node->name);
		}
		goto syscall_cleanup;
	}
	case SYS_FOO_ENTER:
	{
		ret_err = 0;
		kmalloc_create_trace(100);
		goto syscall_cleanup;
	}
	case SYS_FOO_LEAVE:
	{
		ret_err = 0;
		kmalloc_free_trace();
		goto syscall_cleanup;
	}
	case SYS_TRY:
	{
		if(current_process->syscall_try) {
			ret_err = ESTINV;
		} else {
			current_process->syscall_try = true;
			ret_err = 0;
		}
		goto syscall_cleanup;
	}
	case SYS_EXITTRY:
	{
		if(!current_process->syscall_try) {
			ret_err = ESTINV;
		} else {
			current_process->syscall_try = false;
			ret_err = 0;
		}
		goto syscall_cleanup;
	}
	default:
		ret_err = ENOSUP;
		goto syscall_cleanup;
	}

	syscall_cleanup:

	in_syscall = 0;

	if(ret_err != 0) {
		if(!(current_process->syscall_try)) {
			console_printf("%s did not try:\n", current_process->name);
			console_printf("Syscall: %u (%s), Parameters: (%x, %x, %x, %x, %x)\n", no, syscall_names[no],
			param0, param1, param2, param3, param4);
			console_printf("Failed with error %u (%s)\n", ret_err, get_error_name(ret_err));
			//Kill the process!
			sys_exitprocess();
		}
	}

	debug("%s: %u: %s --> %u\n", current_process->name, no, syscall_names[no],
			ret_err);

	return ret_err;
}
